import std.json;
import std.string;
import std.exception;

// Here config data is in fact stored
private JSONValue root;

// Utility function to check if config tree has supposed layout
// Used only in non-release contract checks
private	void validate()
{
	assert( root.type == JSON_TYPE.OBJECT );

	assert( root.object["irc"].type == JSON_TYPE.OBJECT );
	assert( root.object["irc"].object["name"].type == JSON_TYPE.STRING );
	assert( root.object["irc"].object["password"].type == JSON_TYPE.STRING );
	assert( root.object["irc"].object["server"].type == JSON_TYPE.STRING );
	assert( root.object["irc"].object["channels"].type == JSON_TYPE.STRING );
	assert( root.object["irc"].object["port"].type == JSON_TYPE.INTEGER );

	assert( root.object["rpg"].type == JSON_TYPE.OBJECT );
	assert( root.object["rpg"].object["map_dir"].type == JSON_TYPE.STRING );
	assert( root.object["rpg"].object["map_output_file"].type == JSON_TYPE.STRING );
	assert( root.object["rpg"].object["dice_limit"].type == JSON_TYPE.INTEGER );
}

// Global config variable initialization
void initialize( JSONValue init )
out
{
	validate();
}
body
{
	root = init;
}

void initialize( string data )
out
{
	validate();
}
body
{
	root = parseJSON( data );
}

// Returns value from config by path
T get(T)( in string path )
in
{
	validate();
}
body
{
	// Support function for CTFE of switch statament cases
	static pure nothrow string typeCheckedReturn( string json_type, string type, string member )
	{
		return "case "~json_type~":
			static if ( is( T == "~type~" ) )
				return "~member~";
			else
				throw new Exception(\"Stored config type is not matched (\"~path~\")\");";
	}

	// All nodes but last are treated as path...
	JSONValue confcopy = root;
	auto nodes = path.split("/");
	enforce( nodes.length > 0, "Attempt to get a config balue from an empty path" );
	foreach ( s; nodes[0 .. $-1] )
	{
		confcopy = confcopy.object.get(s,JSONValue.init);
		enforce( confcopy != JSONValue.init, "Path is not valid for stored config ("~path~")");
	}

	// ...and last as value	
	JSONValue tmp = confcopy.object.get( nodes[$-1] , JSONValue.init );
	enforce( tmp != JSONValue.init, "Path is not valid for stored config ("~path~")");
	final switch (tmp.type)
	{
		mixin( typeCheckedReturn( "JSON_TYPE.STRING", "string","tmp.str") );
		mixin( typeCheckedReturn( "JSON_TYPE.INTEGER", "long","tmp.integer") );
		mixin( typeCheckedReturn( "JSON_TYPE.FLOAT", "real","tmp.floating") );
		mixin( typeCheckedReturn( "JSON_TYPE.ARRAY", "JSONValue[]","tmp.array") );
		mixin( typeCheckedReturn( "JSON_TYPE.OBJECT", "JSONValue[string]","tmp.object") );
		mixin( typeCheckedReturn( "JSON_TYPE.TRUE", "bool","true") );
		mixin( typeCheckedReturn( "JSON_TYPE.FALSE", "bool","false") );

		case JSON_TYPE.NULL:
			throw new Exception("NULL value found instead of a valid node");
	}
}
